Wprowadzenie
Obiekty (objects) są kontenerami danych, co oznacza, że można w nich umieszczać wiele wartości. Zbiór wartości obiektu nazywamy właściwościami obiektu.
Każda właściwość musi mieć nadany unikalny klucz, który jest "nazwą" tej właściwości. Z tego względu możesz spotkać się z określeniem właściwości jako "pary klucz-wartość" (key-value pair). Pod tym względem obiekt można porównać do zbioru zmiennych, z których każda ma inną nazwę i wartość.
Obiekty stosujemy w sytuacjach, gdy chcemy, aby każda wartość miała przypisaną swoją etykietę (klucz). Częstym zastosowaniem są sytuacje, w których potrzebujemy odwoływać się do pojedynczej wartości poza pętlą.
Tworzenie obiektu
Obiekt tworzymy za pomocą nawiasów klamrowych { }. Możemy stworzyć pusty obiekt, lub od razu zapisać w nim właściwości.
const points = {};
console.log('points:', points); // points: {}
const playerColors = {eve: 'blue', bob: 'green'};
console.log('playerColors:', playerColors); // playerColors: {eve: 'blue', bob: 'green'}Standardowo obiekt będziemy zapisywać w stałej const, aby przez przypadek nie nadpisać całego obiektu. Nie jest to przeszkodą, aby zmieniać zawartość obiektu.
Możemy zamknąć klucz w cudzysłowie, jeśli jest niestandardowy – np. zaczyna się od liczby, zawiera spacje lub znaki interpunkcyjne, etc.
const bestScores = {'!best!': 123, '777': 118, 'Mr. Dotts': 103};
console.log('bestScores:', bestScores); // bestScores: {!best!: 123, 777: 118, Mr. Dotts: 103}Dodawanie właściwości do obiektu
Możemy dodawać nowe właściwości za pomocą jednego z dwóch zapisów – wykorzystując kropkę . lub nawiasy kwadratowe [ ].
points.eve = 15;
points['bob'] = 17;
console.log('points:', points); // points: {eve: 15, bob: 17}Odczytywanie właściwości obiektu
Odczytywanie właściwości z obiektu odbywa się bardzo podobnie do ich zapisywania – również do wyboru mamy kropkę . oraz nawiasy kwadratowe [ ].
const evesPoints = points.eve;
console.log('evesPoints:', evesPoints); // evesPoints: 15
const bobsPoints = points['bob'];
console.log('bobsPoints:', bobsPoints); // bobsPoints: 17W przypadku, gdy klucz mamy zapisany w zmiennej, wykorzystujemy nawiasy kwadratowe [ ].
const bestScore777Key = '777';
const bestScore777 = bestScores[bestScore777Key];
console.log('bestScore777:', bestScore777); // bestScore777: 118Nadpisywanie właściwości
Częstą sytuacją jest zmiana wartości dla pewnego klucza. W przykładach użyliśmy obiektu points – w poniższym przykładzie zmienimy wartości punktów.
points['eve'] = 19;
console.log('points:', points); // points: {eve: 19, bob: 17}
points.bob++;
console.log('points:', points); // points: {eve: 19, bob: 18}
const evesKey = 'eve';
colors[evesKey] = 'black';
console.log('playerColors:', playerColors); // playerColors: {eve: 'black', bob: 'green'}Czy właściwość istnieje
Do sprawdzenia, czy istnieje właściwość o danej nazwie (kluczu), możemy użyć metody hasOwnProperty, która zwróci prawdę true lub fałsz false.
const keyBobExists = points.hasOwnProperty('bob');
console.log('keyBobExists:', keyBobExists); // keyBobExists: true
const keyRobinExists = points.hasOwnProperty('robin');
console.log('keyRobinExists:', keyRobinExists); // keyRobinExists: falseIterowanie po obiekcie (pętle)
Kiedy potrzebujemy wykonać jakieś operacje dla wszystkich właściwości obiektu, możemy wykorzystać pętlę for...in.
Przykłady użycia pętli do iterowania po obiekcie znajdziesz w rozdziale Pętle.
Metody obiektu
Do tej pory mówiliśmy o właściwościach obiektu. Przyjęło się, że właściwością nazywamy tylko taką parę klucz-wartość, w której wartość nie jest funkcją.
Dla odmiany, jeśli wartość jest funkcją, to taką parę klucz-funkcja nazywamy metodą. Przykładem metody może być np.
const calculate = {
add: function(a, b){
return a + b;
}
};
const sumOfNumbers = calculate.add(3, 7);
console.log('sumOfNumbers:', sumOfNumbers); // sumOfNumbers: 10Metodami są również np. document.querySelector, czy Math.random – są one wbudowane w silnik JS przeglądarki i nie musimy ich tworzyć, ale również je nazywamy metodami. Możesz założyć, że metodą jest każda funkcja, której nazwę piszemy po kropce.
Obiekty wielowymiarowe
Wartościami właściwości obiektu mogą być np. liczby czy teksty, ale również obiekty i tablice. Dzięki temu możesz tworzyć wielopoziomowe struktury danych, takie jak:
const gameData = {
currentPlayer: 'eve',
players: {
eve: {
points: 17,
color: 'blue',
},
},
bestScores: [
{
player: '!best!',
points: 123,
},
{
player: '777',
points: 118,
},
{
player: 'Mr. Dotts',
points: 103,
},
],
};Jeśli zastanawiasz się, czy w tym przykładzie nie wstawiliśmy za dużo przecinków, przeczytaj następny rozdział.
Przecinki
W powyższym przykładzie dodaliśmy przecinki nie tylko pomiędzy elementami tablic/obiektów, ale również po ostatnim z nich. Taki zapis jest poprawny w JS, i zalecany szczególnie w przypadku formatowania wieloliniowego, w którym tablica/obiekt zajmuje więcej niż jedną linią.
Stawiając przecinek po każdym elemencie, sprawiamy że wszystkie są tak samo traktowane, więc nie będziemy mieli problemu dodając kolejny element lub zmieniając ich kolejność.
Kopiowanie obiektów
Częstym problemem przy korzystaniu z obiektów jest brak zrozumienia przypisania obiektu do stałej/zmiennej. Spójrz na poniższy przykład, w którym porównujemy dwa przypadki – przypisywania liczb oraz obiektów.
let pointsEve = 5;
let pointsBob = pointsEve;
pointsBob += 2;
console.log('Eve:', pointsEve, 'Bob:', pointsBob);
// Eve: 5 Bob: 7;
let eve = {name: 'Eve', points: 5};
let bob = eve;
bob.name = 'Bob';
eve.points += 2;
console.log(bob);
// {name: 'Bob', points: 7}
console.log(eve);
// {name: 'Bob', points: 7}To nie jest pomyłka – niezależnie od tego czy zmieniamy eve czy bob, zmienią się zarówno eve jak i bob!
Wynika to z faktu, że w stałej/zmiennej nie jest de facto zapisany obiekt, ale odwołanie do niego (pointer). Dlatego wyrażenie bob = eve "kopiuje" tylko odniesienie do obiektu, a nie cały obiekt. Innymi słowy, nie mamy tutaj dwóch obiektów, tylko jeden – do którego odnosi się zarówno zmienna eve, jak i bob.
W rezultacie, w obu zmiennych zapisane jest odniesienie do tego samego obiektu. Dlatego zmieniając którykolwiek z nich, zmieniamy obydwa.
Możemy poradzić sobie z tym problemem klonując obiekt. W JS nie ma funkcji przeznaczonej stricte do klonowania obiektów, dlatego musimy poradzić sobie odrobinę "na około".
eve = JSON.parse(JSON.stringify(bob));
eve.name = 'Eve';
eve.points = 9;
console.log(bob);
// {name: 'Bob', points: 7}
console.log(eve);
// {name: 'Eve', points: 9}Wykorzystaliśmy tutaj bibliotekę JSON do przekonwertowania obiektu bob na tekst (inaczej: ciąg znaków, string) z danymi w formacie JSON. Następnie, natychmiast użyliśmy JSON.parse do wygenerowania nowego obiektu, w oparciu o ten ciąg znaków. W ten sposób uzyskaliśmy drugi obiekt, będący kopią pierwszego.
Jest to tzw. głęboka kopia (deep copy), czyli skopiuje nam obiekty na wszystkich poziomach – również obiekty zapisane we właściwościach klonowanego obiektu.
Rozszerzanie obiektów
JavaScript oferuje sposób na rozszerzanie obiektów, tzn. łączenie dwóch obiektów w taki sposób, aby właściwości drugiego z nich zostały dodane do pierwszego. Służy do tego metoda Object.assign.
const favorites = {
food: 'hot-dog',
car: 'Mustang',
music: 'Pink Floyd',
};
const newFavorites = {
food: 'salads',
sport: 'jogging',
};
Object.assign(favorites, newFavorites);
console.log(favorites);
/*
{
food: 'salads',
car: 'Mustang',
music: 'Pink Floyd',
sport: 'jogging'
}
*/Jak widzisz, właściwości których nie było w newFavorites pozostały niezmienione, a pozostałe zostały zmienione lub dodane.
Metodę Object.assign można też wykorzystywać do tzw. płytkiego kopiowania (shallow copy) obiektów, jeśli pierwszym argumentem będzie pusty obiekt { }. Różni się ono od głębokiego kopiowania tym, że obiekty zapisane we właściwościach kopiowanego obiektu nie zostaną skopiowane. W rezultacie właściwości zarówno oryginalnego, jak i skopiowanego obiektu będą wskazywać na ten sam obiekt. Lepiej zrozumiesz to analizując poniższy przykład.
const johnsHouse = {
windows: 10,
rooms: {
living: 1,
bedroom: 3,
bathroom: 2,
},
};
const marksHouse = Object.assign({}, johnsHouse);
marksHouse.windows = 15;
marksHouse.rooms.bedroom = 4;
console.log(marksHouse);
/*
{
windows: 15,
rooms: {
living: 1,
bedroom: 4,
bathroom: 2
}
}
*/
console.log(johnsHouse);
/*
{
windows: 10,
rooms: {
living: 1,
bedroom: 4,
bathroom: 2
}
}
*/W powyższym przykładzie nie zmienialiśmy żadnych właściwości johnsHouse, więc wydawałoby się, że powinny być takie same jak w momencie deklaracji tego obiektu.
Niestety, ponieważ Object.assign kopiuje płytko, to johnsHouse i marksHouse są dwoma osobnymi obiektami, ale już johnsHouse.rooms i marksHouse.rooms wskazują na ten sam obiekt. Dlatego zmiana marksHouse.rooms.bedroom będzie powodować jednoczesną zmianę johnsHouse.rooms.bedroom.